package bliffoscope.engine;

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */



import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author ddatta
 */
public class Bliffoscope {
    
    
    private List<Subject> subjects ;        // to hold the objects to be searched for as well as the input bliff in the first location
    private Subject inputData = null;       // placeholder for the input bliff.
    

    /**
     * @param args the command line arguments
     * this method runs the Bliffoscope engine from command line
     * the parameters are the input bliff, followed by the bliff names of the objects to be found and followed by the threshhold level and searchChar (+)
     */
    public static void main(String[] args) {
        
        
        try {
            // the following section r
            Bliffoscope blif = new Bliffoscope();
            List<String> fileNames = new ArrayList<String>(args.length-2);
            for(int i=0;i<args.length-2;i++)
                fileNames.add(args[i]);
            
            HashMap<String,Subject> targetSet = blif.readFilesFromNames(fileNames,args[args.length-1]);
            List<Target> allTargets = blif.analyseData(targetSet,Integer.parseInt(args[args.length-2]));
            
            
        } catch (FileNotFoundException ex) {
            Logger.getLogger(Bliffoscope.class.getName()).log(Level.SEVERE, null, ex);
            System.err.println("File Not found");
        } catch (IOException ex) {
            Logger.getLogger(Bliffoscope.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        
    }
    
    /**
     * 
     * @param files
     * @param searchChar
     * @param tolerance
     * @return returns a List<Target> with a rough box of coordinates represented as (top,left) - (bottom,right) and tolerance and type
     * this method is called from a Gui program such as MainFrame to scan the bliff.
     */
    public List<Target> scan(List<File> files, String searchChar, int tolerance){
        try {
            HashMap<String,Subject> targetSet = this.readFiles(files, searchChar);
            List<Target> allTargets = this.analyseData(targetSet,tolerance);
            return allTargets;
        } catch (FileNotFoundException ex) {
            Logger.getLogger(Bliffoscope.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
        
    }
    
    /**
     * this method does the actual work of scanning the data. This is a private method called from scan
     * @param targetSet
     * @param tolerance
     * @return 
     */
    private List<Target> analyseData(HashMap<String,Subject> targetSet,int tolerance) {
        try {
            List<Target> targets = new ArrayList<Target>();
            List<DataAnalyzer> analyzers = new ArrayList<DataAnalyzer>();
            ExecutorService executor = Executors.newFixedThreadPool(2);
            
            Iterator it = targetSet.entrySet().iterator();
            //identify the first object in taretSet as the input data
            if(it.hasNext())
                inputData = (Subject)((Map.Entry)it.next()).getValue();
            
            while(it.hasNext()){
                Map.Entry pair = (Map.Entry)it.next();
                DataAnalyzer worker = new DataAnalyzer(inputData,(Subject)pair.getValue(), (String)pair.getKey(), tolerance);
                analyzers.add(worker);
                executor.execute(worker);
                //worker.run();
            }
            
            
            // This will make the executor accept no new threads
            // and finish all existing threads in the queue
            executor.shutdown();
            // Wait until all threads are finish
            executor.awaitTermination(60, TimeUnit.SECONDS);
            //System.out.println("Finished all threads");
            
            for(DataAnalyzer da : analyzers)
                targets.addAll(da.getTargetsFound());
            
            
            for(Target t : targets){
                System.out.println(t.toString());
            }
            
            return targets;
        } catch (InterruptedException ex) {
            Logger.getLogger(Bliffoscope.class.getName()).log(Level.SEVERE, null, ex);
        }
        return null;
    }
    
    /**
     * private method to read the files from names and return HashMap of Subject
     * @param fileNames  array of filenames to be read
     * @param searchChar character to be search for in the files.
     * @return           returns a HashMap<String,Subject>
     * @throws FileNotFoundException 
     */
    private HashMap<String,Subject> readFilesFromNames(List<String> fileNames,String searchChar) throws FileNotFoundException{
        List<File> files = new ArrayList<File>(fileNames.size());
        for(String name : fileNames)
            files.add(new File(name));
        return readFiles(files,searchChar);
    }
    /**
     * private method to read the files and return a HashMap of Subject
     * @param   files       input data provided to the bliffoscope. The first file is the test data file
     * The subsequent files are the subjects to be searched for. The last input is the character to be searched.  
     * @param   searchChar  character to be search for in the files.
     * @return              returns a HashMap<String,Subject>
     * @throws FileNotFoundException 
     */
    private HashMap<String,Subject> readFiles(List<File> files, String searchChar) throws FileNotFoundException{
        HashMap<String,Subject> map = new HashMap<String,Subject>();
        try {
            
            
            BufferedReader br = null;
                       
            /**
             * The following block reads the files and creates instances of Subject which contains type, a list of BitSet.
             * The bitset represents the chars in the objects which are ON represented for e.g by a +
             * The bitset is memory efficient and allows for quick matching using AND operations.
             */
            
            for(int i=0;i<files.size();i++){
                if(files.get(i)!=null){
                    FileInputStream fin = new FileInputStream(files.get(i));
                    String line=null, type=null;
                    Subject subject = new Subject();
                    if(fin!=null)
                        br = new BufferedReader(new InputStreamReader(fin));

                    while(br!=null && (line = br.readLine())!=null){
                        //System.out.println(line);
                        BitSet b = new BitSet(line.length());
                        
                        for(int j=0;j<line.length();j++){

                            if(line.charAt(j)==searchChar.charAt(0)){
                                b.set(j);
                            }
                        }
                        subject.add(b);
                        //System.out.println(b);
                    }
                    
                    if(files.get(i).getName().indexOf(".blf")>0)
                        type = files.get(i).getName().substring(0, files.get(i).getName().indexOf(".blf"));
                    else
                        type = files.get(i).getName();
                    subject.setType(type);
                    map.put(type, subject);
                    if(this.subjects==null)
                        subjects = new ArrayList<Subject>();
                    subjects.add(subject);
                }
            
                //System.out.println("__________________________________");
            }
                        
            //System.out.println("");
        } catch (IOException ex) {
            Logger.getLogger(Bliffoscope.class.getName()).log(Level.SEVERE, null, ex);
        }
        return map;
    }
    
    
    public List<Subject> getSubjects() {
        return subjects;
    }

    public void setSubjects(List<Subject> subjects) {
        this.subjects = subjects;
    }
}
